package com.adee.gulimall.common.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * spring-cache是spring为了规范管理各种cache实现而创建的项目，主要定义了CacheManager和Cache两个接口。
 * spring-cache使用步骤：
 * 1.添加依赖
 * <dependency>
 *             <groupId>org.apache.commons</groupId>
 *             <artifactId>commons-pool2</artifactId>
 *         </dependency>
 * <dependency>
 *             <groupId>org.springframework.boot</groupId>
 *             <artifactId>spring-boot-starter-data-redis</artifactId>
 *         </dependency>
 * <dependency>
 *             <groupId>org.springframework.boot</groupId>
 *             <artifactId>spring-boot-starter-cache</artifactId>
 *         </dependency>
 * 2.填写配置文件application.yml
 * spring:
 *   redis:
 *     host: 101.42.43.39
 *     database: 0
 *     port: 6379
 *     password: 5UNRltoxlye6Zpjd
 *     timeout: 10000
 *     # 连接池配置
 *     lettuce:
 *       pool:
 *         # 连接池最大连接数
 *         max-active: 8
 *         # 连接池最大空闲连接数
 *         max-idle: 8
 *         # 连接池最小空闲连接数
 *         min-idle: 0
 *         # 连接池最大阻塞等待时间，负值表示没有限制
 *         max-wait: -1
 *         #在关闭客户端连接之前等待任务处理完成的最长时间，在这之后，无论任务是否执行完成，都会被执行器关闭，默认100ms
 *         shutdown-timeout: 100
 *   cache:
 *     redis:
 *       type: redis
 *       time-to-live: 3600000
 *       # 缓存前缀,如果指定了前缀，就用指定的，如果没有，就使用缓存的名字作为前缀
 *       key-prefix: CACHE_
 *       use-key-prefix: true
 *       # 是否缓存null值，防止缓存穿透
 *       cache-null-values: true
 * 3.新建配置类
 * 当前类RedisConfig，配置三个bean：
 * 1） LettuceConnectionFactory，redis连接工厂bean，构造redis连接池来管理连接，连接池基于commons-pool2实现；
 * 2） RedisTemplate，redis业务逻辑操作bean，用于实际数据的增删改查；
 * 3） 缓存管理bean，CacheManager，用于配合spring-cache注解工作
 * 4） 业务类中使用注解
 *      CategoryServiceImpl类上加 @CacheConfig(cacheNames = "my-redis-cache1", cacheManager = "redisCacheManager")
 *          配置缓存策略，使用my-redis-cache1这个策略
 *      CategoryServiceImpl.listWithTree方法上加 @Cacheable(value = {"category"},key = "#root.method.name")
 *
 *      由于增改操作都要使缓存失效，所以在CategoryController的增改方法上加
 *
 * 默认行为：
 *   1）如果缓存存在，方法不调用；
 *   2）key默认自动生成，缓存的名字::SimpleKey []（自动生成的key值）
 *   3）缓存的value的值，默认使用jdk序列化机制，将序列化后的数据存到redis
 *   4）默认ttl为-1，永不过期，不符合缓存一致性的要求。
 * 自定义：
 *   1）指定缓存的key：@Cacheable(key = "#root.method.name")
 *   2）配置文件中配置缓存过期时间：spring.cache.redis.time-to-live: 3600000  #1小时
 *   3）将数据序列化为json格式。
 *
 * 对于一般数据的缓存，spring-cache就够用了，只用@Cacheable和@CacheEvict就可以了。
 */

@Slf4j
@EnableCaching
@Configuration
public class RedisClusterConfig {

    /**
     * redis 支持lettuce拓扑刷新、新ip自动发现
     * @return
     */
    @Bean
    public LettuceClientConfigurationBuilderCustomizer lettuceClientConfigurationBuilderCustomizer() {

        return clientConfigurationBuilder -> {
            // 配置用于开启自适应刷新和定时刷新。如自适应刷新不开启，Redis集群变更时将会导致连接异常
            ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                    //  开启自适应刷新
                    .enableAllAdaptiveRefreshTriggers()
                    .build();

            ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
                    //redis 命令超时时间 根据自己需要设置这里设置为2s
                    .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(20)))
                    //拓扑刷新
                    .topologyRefreshOptions(clusterTopologyRefreshOptions)
                    .autoReconnect(true)
                    .build();
            clientConfigurationBuilder
                    .clientOptions(clusterClientOptions);
        };
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        log.info("RedisConnectionFactory class: {}", redisConnectionFactory.getClass());

        RedisSerializer<Object> serializer = redisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }

    // @Bean
    public RedisSerializer<Object> redisSerializer() {
        // 创建JSON序列化器
        Jackson2JsonRedisSerializer<Object> serializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 必须设置，否则无法将json转化为对象，会转化为Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }

    public RedisSerializer<Object> redisSerializer2() {
        // 创建JSON序列化器
        Jackson2JsonRedisSerializer<Object> serializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 必须设置，否则无法将json转化为对象，会转化为Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }

    // redis缓存
    @Bean("redisCacheManager")
    @Primary
    public CacheManager cacheManager(
            LettuceConnectionFactory lettuceConnectionFactory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        // 配置1
        RedisCacheConfiguration config1 = RedisCacheConfiguration.defaultCacheConfig()
                //缓存失效时间
                .entryTtl(Duration.ofDays(1))
                // 缓存前缀
                .prefixCacheNameWith("CACHE_")
                //key序列化方式
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                //value序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer2()))
                //不允许缓存null值
                .disableCachingNullValues();
        //配置2
        RedisCacheConfiguration config2 = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(1000))
                .prefixCacheNameWith("CACHE_")
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer2()))
                .disableCachingNullValues();

        //设置一个初始化的缓存空间set集合
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("my-redis-cache1");
        cacheNames.add("my-redis-cache2");

        //对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(3);
        configurationMap.put("my-redis-cache1", config1);
        configurationMap.put("my-redis-cache2", config2);

        return RedisCacheManager.builder(lettuceConnectionFactory)
                //默认缓存配置
                .cacheDefaults(config1)
                //初始化缓存空间
                .initialCacheNames(cacheNames)
                //初始化缓存配置
                .withInitialCacheConfigurations(configurationMap).build();
    }

    // redisson分布式锁
    //@Bean
    RedissonClient redissonClient() {
        //集群
        Config config = new Config();
        config.useClusterServers()
                .setConnectTimeout(180000)
                .setTimeout(180000)
                .setPassword("5UNRltoxlye6Zpjd")
                .setScanInterval(2000) // cluster state scan interval in milliseconds
                .addNodeAddress("redis://101.42.43.39:6381", "redis://101.42.43.39:6382","redis://101.42.43.39:6383")
                .addNodeAddress("redis://101.42.43.39:6384", "redis://101.42.43.39:6385","redis://101.42.43.39:6386");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }

//    @Bean
//    public RedisLock redisLockUtil(RedisTemplate<String, Object> redisTemplate) {
//        return new RedisLock(redisTemplate);
//    }
}
